﻿using Red.Core.Network.PacketNetwork;
using System;
using System.Windows;
using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;

namespace Red.CommUtils.Application
{
    /// <summary>
    /// A way to restore application in case of any failure.
    /// </summary>
    public interface IAppRestore
    {
        string CrashFilePath { get; }
        void Restore();
    }

    /// <summary>
    /// Handle to the application. Use wisely. When out of scope, will release remote application handle (sends message to the application),
    /// so application is closed when there are no more references (handles).
    /// </summary>
    public class AppHandle : IAppHandle
    {
        private string _name;
        private string _displayName;
        private string _crashFilePath;
        private IPAddress _address;
        private AppChannels _channels;
        private Process _process = null;
        private IAppRestore _restore;
        ChannelFactory _channelFactory;

        const int mainPort = 37040;
        const int debugPort = 37041;
        const int editorPort = 37042;

        public string Name
        {
            get { return _name; }
        }

        public string DisplayName
        {
            get { return _displayName; }
        }

        public IPAddress Address
        {
            get { return _address; }
        }

        public AppChannels Channels
        {
            get { return _channels; }
        }

        public string CrashFilePath
        {
            get { return _crashFilePath; }
        }

        public void InitializeEditorChannel()
        {
            TcpAddress address = new TcpAddress( IPAddress.Any.ToString() + ":" + editorPort );

            IChannel editorChannel = _channelFactory.CreateListenerChannel( "Editor", address );
            _channels = new AppChannels( editorChannel );
        }

        public async Task<bool> ConnectToChannels()
        {
            TcpAddress addrMain = new TcpAddress( _address.ToString() + ":" + mainPort );
            _channels.InitMainChannel( await _channelFactory.OpenChannel( "Main", addrMain ) );

            TcpAddress addrDebug = new TcpAddress(_address.ToString() + ":" + debugPort );
            _channels.InitDebugChannel( await _channelFactory.OpenChannel( "Debug", addrDebug ) );

            return true;
        }

        // sets local process that can be managed by the editor app
        void SetProcess( Process proc )
        {
            _process = proc;
        }

        public void ForceClose()
        {
            if ( _process != null )
                _process.Kill();
        }

        public void RequestToolkitService()
        {
            comm.OpenToolServices msg = new comm.OpenToolServices();
            msg.port = editorPort;
            msg.ip = "127.0.0.1";

            comm.Message respone = _channels.Main.SendAndWait( msg, 5000 );
            if ( respone == null )
            {
                throw new CommunicationException( "No engine was properly connected to the editor" );
            }

            if ( !respone.IsA( comm.ToolSrvCommand.GetStaticID() ) )
            {
                throw new CommunicationException( "Invalid engine response" );
            }

            comm.ToolSrvCommand serviceCommandMsg = respone as comm.ToolSrvCommand;

            EServiceCommand srvCommand = ( EServiceCommand )serviceCommandMsg.command;

            if ( srvCommand != EServiceCommand.Success )
            {
                throw new CommunicationException( "Failed to open tool services." );
            }
        }

        public async Task<bool> Restore()
        {
            const int timeoutInMilliseconds = 60000;
            _restore.Restore();

            _crashFilePath = _restore.CrashFilePath;

            bool result = await _channels.Main.TryReconnect( timeoutInMilliseconds );
            result = result && await _channels.Debug.TryReconnect( timeoutInMilliseconds );

            return result;
        }

        public AppHandle( string name, string displayName, string crashFilePath, ChannelFactory channelFactory, IPAddress address, IAppRestore restore )
        {
            _name = name;
            _displayName = displayName;
            _address = address;
            _restore = restore;
            _crashFilePath = crashFilePath;
            _channelFactory = channelFactory;
        }

        public void RequestClose()
        {
            try
            {
                // Send release message
                comm.ReleaseApplicationHandle message = new comm.ReleaseApplicationHandle();
                message.reason = "closing editor";
                Channels.Main.Send( message );
            }
            catch ( Exception ex )
            {
                Debug.WriteLine( "Exception occurred when closing app handle: " + ex.Message );
            }
        }
    }
}
